package controlisolation.lifecyclefsm.stateexecutor;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import controlisolation.lifecyclefsm.LifeCycleFSM;

public class StateExecutor {

    private String _name;
    private StateWork _work;
    private List _workStartedListeners, _workFinishedListeners, 
            _workErrorsListeners,_workWarningsListeners,
            _workInfoMessagesListeners;
    private static final Logger _logger =
            Logger.getLogger(StateExecutor.class.getName());
    private LifeCycleFSM _containerFSM;
    private long _workTimeMillis;

    public StateExecutor(String name, LifeCycleFSM fsm) {
        _workStartedListeners = new ArrayList();
        _workFinishedListeners = new ArrayList();
        _workErrorsListeners = new ArrayList();
        _workWarningsListeners = new ArrayList();
        _workInfoMessagesListeners = new ArrayList();
        _name = name;
        _containerFSM = fsm;

    }
    
    public StateExecutor(String name, StateWork work, LifeCycleFSM fsm) {
        this(name,fsm);
        _work=work;
    }

    public StateExecutor(String name, StateWork work) {
        this(name,work,null);
    }
    
    public void doWork() {
        if (_work == null) {
            //_logger.log(Level.INFO, "No work defined for state:" + _name);
            return;
        }


        if ((_containerFSM!=null)&&(_containerFSM.isDebugMessages())) {
            _logger.log(Level.INFO, "STARTING: " + _name);
        }
        _work.getWorkOutputs().clear();
        _work.getWorkErrors().clear();
        _work.getWorkWarnings().clear();
        _work.getWorkInfoMessages().clear();
        
        if ((_containerFSM!=null)&&(_containerFSM.isDebugMessages())) {
            logStateInputInfo();
        }
        fireWorkStartedEvent();

        long start_time = 0;
        long end_time = 0;
        long run_time = 0;

        try {
            start_time = System.currentTimeMillis();

            /* Launch the work in another thread,
             * if it takes more than the allowed time
             * then interrupt it.
             */
            //_work.doWork();
            Thread t = new Thread(_work);
            t.start();
            while (t.isAlive()) {
                pauseFor(_work.getTickMillis());
                end_time = System.currentTimeMillis();
                run_time = end_time - start_time;
                if (_work.getMaxRunningTime() != -1) //-1 meant infinite
                {
                    if (run_time > _work.getMaxRunningTime()) {
                        t.interrupt();
                    }
                }
            }

            end_time = System.currentTimeMillis();
            _workTimeMillis = end_time - start_time;
            _work.increaseTimesRan();
            _work.registerRunTime(run_time);
        } catch (Exception e) {
            WorkStatus status = new WorkStatus(_work);
            status.setStatusMessageType(WorkStatus.StatusMessageType.ERROR);
            status.setAssociatedException(e);
            status.setCode(0);
            status.setDescription("Work generated exception.");
            _work.addError(status);
        }

        if ((_containerFSM!=null)&&(_containerFSM.isDebugMessages())) {
            logStateOutputInfo();
        }
        fireWorkFinishedEvent();
        if (!_work.suscessful()) {
            if ((_containerFSM!=null)&&(_containerFSM.isDebugMessages())) {
                logWorkErrors();
            }

        }

        if (_work.hasWarnings()) {
            if ((_containerFSM!=null)&&(_containerFSM.isDebugMessages())) {
                logWorkWarnings();
            }
        }

        if (_work.hasInfoMessages()) {
            if ((_containerFSM!=null)&&(_containerFSM.isDebugMessages())) {
                this.logWorkInfoMessages();
            }

        }

       

        if ((_containerFSM!=null)&&(_containerFSM.isDebugMessages())) {
            _logger.log(Level.INFO, "FINISHED: " + _name);
            _logger.log(Level.INFO, "Work Time (ms) " + _workTimeMillis);
            _logger.log(Level.INFO, "Ran " + _work.getTimesRan() + " times.");
            _logger.log(Level.INFO, "Average runtime " + _work.getAverageRuntime());
        }
        
        _work.getWorkInputs().clear();
    }

    private void pauseFor(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException ex) {
            _logger.log(Level.SEVERE, null, ex);
        }
    }

    /**
     * @return the _name
     */
    public String getName() {
        return _name;
    }

    /**
     * @param _name the _name to set
     */
    public void setName(String name) {
        this._name = name;
    }

    /**
     * @return the _work
     */
    public StateWork getWork() {
        return _work;
    }

    /**
     * @param work the _work to set
     */
    public void setWork(StateWork work) {
        this._work = work;
        work.setStateExecutor(this);
    }

    public synchronized void addWorkErrorEventListener(WorkErrorEventListener listener) {
        _workErrorsListeners.add(listener);
    }

    public synchronized void removeWorkErrorEventListener(WorkErrorEventListener listener) {
        _workErrorsListeners.remove(listener);
    }
    
    public synchronized void addWorkWarningEventListener(WorkWarningEventListener listener) {
        _workWarningsListeners.add(listener);
    }

    public synchronized void removeWorkWarningEventListener(WorkWarningEventListener listener) {
        _workWarningsListeners.remove(listener);
    }
    
    public synchronized void addWorkInfoMessageEventListener(WorkWarningEventListener listener) {
        _workInfoMessagesListeners.add(listener);
    }

    public synchronized void removeWorkInfoMessageEventListener(WorkInfoMessageEventListener listener) {
        _workInfoMessagesListeners.remove(listener);
    }

    public synchronized void addWorkStartedEventListener(WorkStartedEventListener listener) {
        _workStartedListeners.add(listener);
    }

    public synchronized void removeWorkStartedEventListener(WorkErrorEventListener listener) {
        _workStartedListeners.remove(listener);
    }

    public synchronized void addWorkFinishedEventListener(WorkFinishedEventListener listener) {
        _workFinishedListeners.add(listener);
    }

    public synchronized void removeWorkFinishedEventListener(WorkFinishedEventListener listener) {
        _workFinishedListeners.remove(listener);
    }

    public synchronized void fireWorkErrorEvent(WorkStatus st) {
        //WorkStartedEvent event = new WorkStartedEvent(this._work);
        Iterator i = _workErrorsListeners.iterator();
        while (i.hasNext()) {
            ((WorkErrorEventListener) i.next()).handleWorkErrorEvent(st);
        }
    }
    
    public synchronized void fireWorkWarningEvent(WorkStatus st) {
        //WorkStartedEvent event = new WorkStartedEvent(this._work);
        Iterator i = _workWarningsListeners.iterator();
        while (i.hasNext()) {
            ((WorkWarningEventListener) i.next()).handleWorkWarningEvent(st);
        }
    }
    
     public synchronized void fireWorkInfoMessageEvent(WorkStatus st) {
        //WorkStartedEvent event = new WorkStartedEvent(this._work);
        Iterator i = _workInfoMessagesListeners.iterator();
        while (i.hasNext()) {
            ((WorkInfoMessageEventListener) i.next()).handleWorkInfoMessageEvent(st);
        }
    }
    
    private synchronized void fireWorkStartedEvent() {
        //WorkStartedEvent event = new WorkStartedEvent(this._work);
        Iterator i = _workStartedListeners.iterator();
        while (i.hasNext()) {
            ((WorkStartedEventListener) i.next()).handleWorkStartedEvent(_work);
        }
    }

    private synchronized void fireWorkFinishedEvent() {
        //WorkFinishedEvent event = new WorkFinishedEvent(this._work);
        Iterator i = _workFinishedListeners.iterator();
        while (i.hasNext()) {
            ((WorkFinishedEventListener) i.next()).handleWorkFinishedEvent(_work);
        }
    }

    

    public void addWorkInput(String name, Object value) {
        this._work.getWorkInputs().put(name, value);
    }

    public void setWorkInputs(Map<String, Object> work_inputs) {
        this._work.setWorkInputs(work_inputs);
    }

    public Map<String, Object> getWorkOutputs() {
        return this._work.getWorkOutputs();
    }

    public List<WorkStatus> getWorkErrors() {
        return this._work.getWorkErrors();
    }

    public void logWorkStatus()
    {     
        logStateInputInfo();
        logStateOutputInfo();
        logWorkInfoMessages();
        logWorkWarnings();
        logWorkErrors(); 
    }
    
    private void logWorkErrors() {
        if (!this._work.getWorkErrors().isEmpty()) {
            _logger.log(Level.SEVERE, "WORK ERRORS:");
            for (WorkStatus e : _work.getWorkErrors()) {
                _logger.log(Level.SEVERE, "\t" + e.toString());
            }

        }
    }

    private void logWorkWarnings() {

        if (this._work.getWorkWarnings() != null) {
            _logger.log(Level.WARNING, "WORK WARNING MESSAGES:");
        }

        for (WorkStatus e : _work.getWorkWarnings()) {
            _logger.log(Level.WARNING, "\t" + e.toString());
        }

    }

    private void logWorkInfoMessages() {

        if (this._work.getWorkInfoMessages() != null) {
            _logger.log(Level.INFO, "WORK INFO MESSAGES:");
        }

        for (WorkStatus e : _work.getWorkInfoMessages()) {
            _logger.log(Level.INFO, "\t" + e.toString());
        }

    }

    private void logStateInputInfo() {

        _logger.log(Level.INFO, StateWork.toStringWorkInputs(this.getWork()));
    }

    private void logStateOutputInfo() {

        _logger.log(Level.INFO, StateWork.toStringWorkOutputs(this.getWork()));
    }

    public LifeCycleFSM getContainerFSM() {
        return _containerFSM;
    }

    public long getWorkTimeMillis() {
        return _workTimeMillis;
    }
}
